#pragma once

#include <cstddef>
#include <stdexcept>
template <typename T>
class SharedPtr {
private:
    std::size_t * _ref_count;
    T * _p;
public:
    SharedPtr();
    explicit SharedPtr(T * newone);
    ~SharedPtr();

    SharedPtr(const SharedPtr& other ) {
        if( this != &other ) {
            (*(other._ref_count))++;
            _ref_count = other._ref_count;
            _p = other._p;
        }
    }
    SharedPtr(SharedPtr && other) {
        if ( this != &other) {
            _ref_count = other._ref_count;
            _p = other._p;

            other._p = nullptr;
            _ref_count = nullptr;
        }
    }

    T* get();
    std::size_t use_count();
    void reset();
    void reset(T*);
    T& operator*() {
        if( _p ==nullptr )
            throw std::runtime_error("tried to resolve null pointer");
        return *_p;
    }
    T* operator->() {
        if( _p ==nullptr )
            throw std::runtime_error("tried to resolve null pointer");
        return _p;
    }
    explicit operator bool() {
        return _p != nullptr;
    }
    SharedPtr& operator=( const SharedPtr & other ) {
        if( this != &other ) {
            if( this->_p != other._p ) {
                if( this->_p )
                    delete this->_p;
                this->_p = other._p;
                if(this->_ref_count)
                    delete this->_ref_count;
                this->_ref_count = other._ref_count;
                (*(this->_ref_count)) ++;
            }
        }
        return *this;
    }
};

template <typename T>
SharedPtr<T>::SharedPtr() : _p(nullptr) {
    _ref_count = new std::size_t;
    if( !_ref_count )
        throw std::runtime_error(" no free memory to create new shared pointer \n");
    *_ref_count = 1;
}

template<typename T>
SharedPtr<T>::SharedPtr(T * newone) : _p{newone} {
    _ref_count = new std::size_t;
    if( !_ref_count )
        throw std::runtime_error(" no free memory to create new shared pointer \n");
    *_ref_count = 1;
}

template <typename T>
SharedPtr<T>::~SharedPtr() {
    *_ref_count -= 1;
    if( *_ref_count == 0) {
        if(_p)
            delete _p;
        _p = nullptr;
    }
}

template <typename T>
T* SharedPtr<T>::get() {
    return _p;
}

template <typename T>
std::size_t SharedPtr<T>::use_count() {
    return *_ref_count;
}

template <typename T>
void SharedPtr<T>::reset() {
    if(_p)
        delete _p;
    _p = nullptr;
    if(*_ref_count == 1 )
        *_ref_count = 0;
}

template <typename T>
void SharedPtr<T>::reset(T * newone) {
    if( newone == nullptr)
        throw std::runtime_error("reset with empty");
    if( newone == _p) {
        if( *_ref_count == 1 ) {
            delete _p;
            *_ref_count = 0;
        }
        return;
    }
    if(_p)
        delete _p;
    _p = newone;
}

template <typename T, typename ...Args>
SharedPtr<T> make_shared(Args ...args) {
    T * newone = new T(args...);
    return SharedPtr( newone );
}